﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 13/06/2012
 * Time: 20:47
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;

namespace CSharpExt.Data
{
        public class DataCommandException : DbException
    {
        #region Constructor
        
        public DataCommandException(String command) : base(command)
        {
            
        }
        
        public DataCommandException(DataCommand dc, Exception e) : base(dc.ToString(), e)
        {
            
        }
        
        #endregion
    }
    
    // ----------------------------------------------------------------------
    // DataCommandParameters
    //  ----------------------------------------------------------------------
    
    internal class DataCommandParameters
    {
        #region Private Data Structure
        
        class ParameterInfo
        {
            internal String Name;
            internal bool Assigned;
            //internal Object Value;
        }
        
        #endregion
        
        #region Private Data
        
        private String _command;
        
        private Dictionary<String, ParameterInfo> _params = new Dictionary<String, DataCommandParameters.ParameterInfo>();
        
        #endregion
        
        #region Constructor
        
        internal DataCommandParameters(String command)
        {
            _command = command;
        }
        
        #endregion
        
        #region Properties
        
        internal String Command
        {
            get { return _command; }
        }
        
        #endregion
        
        #region Operations
        
        internal String Parse()
        {
            String parsed = _command;
            int pos;
            while((pos = parsed.IndexOf('{')) != -1)
            {
                int end = parsed.IndexOf('}', pos+1);
                if(end == -1)
                    throw new DataCommandException(String.Format("Syntax error: '}' in '{0}'", _command));
                
                String name = parsed.Substring(pos+1, end-pos-1);
                parsed = parsed.Substring(0, pos) + "@" + name + parsed.Substring(end+1);
                
                this.Add(name);
            }
            return parsed;
        }
        
        private void Add(String name)
        {
            if (!_params.ContainsKey(name))
                _params[name] = new ParameterInfo(){ Name = name };
        }
        
        internal void Assigned(String name)
        {
            if (!_params.ContainsKey(name))
                throw new DataCommandException(String.Format("Parameter '{0}' not defined in '{1}'", name, _command));
            if(_params[name].Assigned)
                throw new DataCommandException(String.Format("Parameter '{0}' already assigned in '{1}'", name, _command));
            _params[name].Assigned = true;
        }
        
        internal void CheckAssigned()
        {
            int notassigned = 0;
            foreach(ParameterInfo pinfo in _params.Values)
            {
                if (!pinfo.Assigned)
                    notassigned++;
            }
            
            if (notassigned == 0)
                return;
            
            String message = String.Format("Missing assigned parameter(s) {0} in {1}", 
                                           composeNotAssigned(), 
                                           _command);
            
            throw new DataCommandException(message);
        }
        
        internal String AssignedParameters(DbParameterCollection dbcparams)
        {
            return composeAssigned(dbcparams);
        }
        
        #endregion
        
        #region Implementation
        
        private String composeNotAssigned()
        {
            List<String> notassigned = null;
            foreach(ParameterInfo pinfo in _params.Values)
            {
                if (pinfo.Assigned)
                    continue;
                
                if (notassigned == null)
                    notassigned = new List<String>();
                
                notassigned.Add(pinfo.Name);
            }
            
            if (notassigned == null)
                return String.Empty;
            
            StringBuilder sb = new StringBuilder("{");
            IEnumerator<String> it = notassigned.GetEnumerator();
            it.MoveNext();
            sb.AppendFormat("{0}", it.Current);
            
            while(it.MoveNext())
            {
                sb.Append(", ");
                sb.AppendFormat("{0}", it.Current);
            }
            sb.Append("}");
            return sb.ToString();
        }
        
        private String composeAssigned(DbParameterCollection dbcparams)
        {
            if (_params.Count == 0)
                return String.Empty;
            
            StringBuilder sb = new StringBuilder("{");
            IEnumerator<ParameterInfo> it = _params.Values.GetEnumerator();
            it.MoveNext();
            sb.AppendFormat("{0}={1}", it.Current.Name, dbcparams["@"+it.Current.Name].Value);
            while(it.MoveNext())
            {
                sb.Append(", ");
                sb.AppendFormat("{0}={1}", it.Current.Name, dbcparams["@"+it.Current.Name].Value);
            }
            sb.Append("}");
            return sb.ToString();
        }
        
        #endregion
    }
    
    
    // ----------------------------------------------------------------------
    // DataCommand
    //  ----------------------------------------------------------------------
    
    /// <summary>
    /// Description of DataCommand.
    /// </summary>
    public class DataCommand
    {
        #region Private Data
        
        private CommandType _commandType;
        
        private DataConnection _conn;
        
        private String _commandText;
        
        private DataCommandParameters _params;
        
        private DbCommand _command;
        
        private MetadataCommand _meta;
        
        private List<DataResultSet> _rsets = new List<DataResultSet>();
        
        #endregion
        
        #region Constructor
        
//        internal DataCommand(DataConnection conn, DbCommand cmd, DataCommandParameters dcparams)
//        {
//            _conn = conn;
//            _command = cmd;
//            _params = dcparams;
//        }

        internal DataCommand(DataConnection conn, String command, CommandType commandType)
        {
            _commandType = commandType;
            _conn = conn;
            _command = _conn.InnerConnection.CreateCommand();
            _commandText = command;
        }
        
        #endregion
        
        #region Properties
        
        public MetadataCommand Metadata
        {
            get 
            {
                if (_meta == null)
                    _meta = new MetadataCommand(this);
                return _meta;
            }
        }
        
        public DataConnection Connection
        {
            get { return _conn; }
        }
        
        // ------------------------------------------------------------------
        
        internal DbCommand InnerCommand
        {
            get { return _command; }
        }
        
        #endregion
        
        #region Operations
        
        internal void ParseCommand()
        {
            _params = new DataCommandParameters(_commandText);
            
            String command = _params.Parse();
            
            _command.CommandText = command;
            
            // Text             -> comando SQL
            // StoredProcedure  -> nome della stored procedure
            // TableDirect      -> nome della tabella e non 'SELECT * FROM table'
            
            _command.CommandType = _commandType;
        }
        
        public DataResultSet ExecuteQuery()
        {
            _params.CheckAssigned();
            
            // Default
            // SingleResult
            // SchemaOnly
            // KeyInfo
            // SingleRow
            // SequentialAccess
            // CloseConnection
            
            try
            {
                DbDataReader reader = _command.ExecuteReader();
            
                DataResultSet drs = new DataResultSet(this, reader);
                
                addResultSet(drs);
                
                return drs;
            }
            catch(Exception e)
            {
                throw new DataCommandException(this, e);
            }
        }
        
        public int ExecuteCommand()
        {
            _params.CheckAssigned();
            
            try
            {
                return _command.ExecuteNonQuery();
            }
            catch(Exception e)
            {
                throw new DataCommandException(this, e);
            }
        }
        
        public Object ExecuteScalar()
        {
            _params.CheckAssigned();
            
            try
            {
                return _command.ExecuteScalar();
            }
            catch(Exception e)
            {
                throw new DataCommandException(this, e);
            }
        }
        
        public void Close()
        {
            closeResutSets();
            
            _conn.removeCommand(this);
        }
        
        #endregion
        
        #region Parameter Handling
        
        public DataCommand SetBoolean(String paramName, bool value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        public DataCommand SetInteger(String paramName, int value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        public DataCommand SetLong(String paramName, long value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        public DataCommand SetDouble(String paramName, double value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        public DataCommand SetString(String paramName, String value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        public DataCommand SetDateTime(String paramName, DateTime value)
        {
            _params.Assigned(paramName);
            
            DbParameter param = _command.CreateParameter();
            param.ParameterName = "@" + paramName;
            param.Value = value;
            _command.Parameters.Add(param);
            return this;
        }
        
        #endregion

        #region Implementation
        
        private void addResultSet(DataResultSet drs)
        {
            _rsets.Add(drs);
        }
        
        internal void removeResultSet(DataResultSet drs)
        {
            _rsets.Remove(drs);
        }
        
        private void closeResutSets()
        {
            if (_rsets.Count == 0)
                return;
            
            List<DataResultSet> rsets = new List<DataResultSet>(_rsets);
            foreach(DataResultSet drs in rsets)
            {
                drs.Close();
            }
        }
        
        #endregion
 
        #region Overrides
        
        public override string ToString()
        {
            return String.Format("{0}/{1}", _params.Command, _params.AssignedParameters(_command.Parameters));
        }
 
        #endregion
    }
}
